use rocket::{
    http::Status,
    response::{self, status, Responder},
    serde::json::Json,
    Request, Response, Route,
};
use rocket_db_pools::{
    sqlx::{self, Acquire},
    Connection,
};
use serde::Deserialize;

use crate::{
    action::user::{UpdatePasswordError, UserIdExt},
    data::message::Message,
    fairing::user::UserDb,
    model::user::{LoginSecret, PasswordSecret, UserData},
};

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct UpdatePasswordData {
    old_password: Option<String>,
    new_password: String,
}

#[post("/password/update", data = "<data>")]
async fn update_self_password(
    mut db: Connection<UserDb>,
    user: UserData,
    data: Json<UpdatePasswordData>,
) -> Result<status::NoContent, UpdateSelfPasswordError> {
    if data.new_password.is_empty() {
        return Err(UpdateSelfPasswordError::InvalidInput);
    }

    let mut tx = db.begin().await?;
    let old_password = data.0.old_password.filter(|password| !password.is_empty());
    let old_secret = old_password.map(PasswordSecret);
    let old_credentals = user.identifier.id.get_credentials(&mut tx).await?;
    if let Some(old_credentals) = old_credentals.password {
        if let Some(old_secret) = old_secret {
            if !old_secret.check(&old_credentals).await {
                return Err(UpdateSelfPasswordError::WrongPassword);
            }
        } else {
            return Err(UpdateSelfPasswordError::WrongPassword);
        }
    }

    user.identifier
        .id
        .update_user_password(&mut tx, PasswordSecret(data.0.new_password))
        .await?;

    tx.commit().await?;

    Ok(status::NoContent)
}

enum UpdateSelfPasswordError {
    WrongPassword,
    DatabaseError(sqlx::Error),
    SaltRngFailed(rand::Error),
    InvalidInput,
}

impl From<UpdatePasswordError> for UpdateSelfPasswordError {
    fn from(value: UpdatePasswordError) -> Self {
        match value {
            UpdatePasswordError::EmptyPassword => UpdateSelfPasswordError::InvalidInput,
            UpdatePasswordError::DatabaseError(err) => UpdateSelfPasswordError::DatabaseError(err),
            UpdatePasswordError::SaltRngFailed(err) => UpdateSelfPasswordError::SaltRngFailed(err),
        }
    }
}

impl From<sqlx::Error> for UpdateSelfPasswordError {
    fn from(value: sqlx::Error) -> Self {
        UpdateSelfPasswordError::DatabaseError(value)
    }
}

impl From<rand::Error> for UpdateSelfPasswordError {
    fn from(value: rand::Error) -> Self {
        UpdateSelfPasswordError::SaltRngFailed(value)
    }
}

impl<'r> Responder<'r, 'static> for UpdateSelfPasswordError {
    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
        match self {
            UpdateSelfPasswordError::WrongPassword => Response::build_from(
                Json(Message {
                    message: String::from("原密码不匹配"),
                })
                .respond_to(req)?,
            )
            .status(Status::PreconditionFailed)
            .ok(),
            UpdateSelfPasswordError::DatabaseError(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
            UpdateSelfPasswordError::SaltRngFailed(err) => Response::build_from(
                Json(Message {
                    message: err.to_string(),
                })
                .respond_to(req)?,
            )
            .status(Status::InternalServerError)
            .ok(),
            UpdateSelfPasswordError::InvalidInput => Response::build_from(
                Json(Message {
                    message: String::from("输入为空"),
                })
                .respond_to(req)?,
            )
            .status(Status::PreconditionFailed)
            .ok(),
        }
    }
}

pub fn route() -> Vec<Route> {
    routes![update_self_password]
}
